#include "DropletRenderer.h"

#ifdef _LAB_1
const char *shaderDirectory = "E:\\Documents\\My Dropbox\\Work\\Grad_School\\Lab\\Droplets\\DropletSimDemos\\DropletRenderer\\Shaders\\";
const char *modelDirectory  = "E:\\Documents\\My Dropbox\\Work\\Grad_School\\Lab\\Droplets\\DropletSimDemos\\DropletRenderer\\Models\\";
#endif

#ifdef _HOME
const char *shaderDirectory = "G:\\Documents\\GoogleCode\\Droplets\\cu-droplet\\DropletSimDemos\\DropletRenderer\\Shaders\\";
const char *modelDirectory  = "G:\\Documents\\GoogleCode\\Droplets\\cu-droplet\\DropletSimDemos\\DropletRenderer\\Models\\";
#endif

DropletRenderer::DropletRenderer() 
	: OGLRenderer()		// Base class constructor
{
	firstUpdate = true;
	renderableObjects.clear();
	srand((unsigned)time(0));	// Seed the random number generator
}

DropletRenderer::~DropletRenderer() { return; }

OGLResult DropletRenderer::LoopInitialize()
{
	// NOTE : It is VERY IMPORTANT that is be the first function called when initializing the renderer.
	// Initialize the Droplet Simulator
	initSimulator();
	setupSimObjects();

	dropletColors = new std::vector<unsigned char *>();
	dropletPos = new std::vector<float *>();
	for(int i = 0; i < numDroplets; i++)
	{
		unsigned char *tmp1 = (unsigned char *)malloc(sizeof(unsigned char) * 3);
		dropletColors->push_back(tmp1);
		float *tmp2 = (float *)malloc(sizeof(float) * 2);
		dropletPos->push_back(tmp2);
	}


	// Create and bind a Vertex Array Object
	createVAO();
	glBindVertexArray(vaoID);

	// Create and use a Shader Program
	std::string vertexShaderPath = std::string(shaderDirectory);
	std::string fragmentShaderPath = std::string(shaderDirectory);
	loadProgram(vertexShaderPath.append(VERTEX_SHADER_FILE),
		fragmentShaderPath.append(FRAGMENT_SHADER_FILE));

	// Setup the renderable and phyiscs objects - Floor, Walls & Droplets
	objectCounter = 0;
	setupFloor();
	setupWalls();
	setupDroplets();

	return OGL_SUCCESS;
}

OGLResult DropletRenderer::LoopIterate()
{		
	// Clear Buffers
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	// Set the camera and schene params
	glm::mat4 pvMat = viewControl->GetPVMat();
	GLuint pvMatID = glGetUniformLocation(programID, "PV");
	glUniformMatrix4fv(pvMatID, 1, GL_FALSE, &pvMat[0][0]);

	// Render the models
	std::vector<RenderableObject *>::iterator it;
	for(it = renderableObjects.begin();
		it != renderableObjects.end();
		it++)
	{
		RenderableObject *rObj = *it;
		rObj->Render(vaoID, programID);
	}

	// Step the simulator
	sim.Step();

	return OGL_SUCCESS;
}

OGLResult DropletRenderer::LoopUpdate()
{
	// FIXED BUG : This 'if' code-block will only run once. Nothing gets rendered otherwise.
	if(firstUpdate)
	{
		firstUpdate = false;
		viewControl->FirstUpdate();
	}

	sim.GetDropletColors(dropletColors);
	sim.GetDropletPositions(dropletPos);


	// NOTE : 'i' skips over tiles & walls and starts at droplets
	int j = 0;
	for(int i = (numColTiles * numRowTiles) + (2 * (numColTiles + numRowTiles)); 
		i < renderableObjects.size(); 
		i++)
	{
		RenderableDroplet *rd = (RenderableDroplet *)renderableObjects[i];

		//rd->geomData->SetRotationMat(
		//	dropletAngle,
		//	glm::vec3(0.0f, 0.0f, 1.0f));

		rd->geomData->SetTranslationMatAbsolute(
			glm::vec3(dropletPos->at(j)[0], dropletPos->at(j)[1], 0.0f));
		
		// Normalize the color values
		rd->UpdateColor(
			static_cast<float>(dropletColors->at(j)[0]) / 256.0f, 
			static_cast<float>(dropletColors->at(j)[1]) / 256.0f, 
			static_cast<float>(dropletColors->at(j)[2]) / 256.0f, 
			1.0f);
		j++;
	}

	// TODO : Put these handlers in a timer later
	// Scan for user input
	viewControl->KeyboardActionHandler();
	viewControl->MouseActionHandler();

	// Swap render buffers
	glfwSwapBuffers();

	return OGL_SUCCESS;
}

OGLResult DropletRenderer::LoopComplete()
{
	// Destroy Vertex Bufffer Objects
	OGLResult retval = OGL_SUCCESS;

	std::vector<RenderableObject *>::iterator it;
	for(it = renderableObjects.begin();
		it != renderableObjects.end();
		it++)
	{
		RenderableObject *rObj = *it;
		retval = rObj->Cleanup();
		if(retval != OGL_SUCCESS)
		{
			fprintf(stderr, "[ERROR] Could not delete Vertex Buffer Object with index %u",
				rObj->objData->GetVBOID());
		}

		delete rObj;
	}
	renderableObjects.clear();

	// Destroy Shaders
	retval = destroyShadersAndPrograms();
	if(retval != OGL_SUCCESS)
	{
		fprintf(stderr, "[ERROR] Could not delete Program with index %u", programID);
	}

	// Destroy Vertex Array Objects
	glBindVertexArray(0);
	glDeleteVertexArrays(1, &vaoID);


	// Cleanup the simulator
	sim.Cleanup();
	std::vector<unsigned char *>::reverse_iterator cit;
	for(cit = dropletColors->rbegin(); cit != dropletColors->rend(); cit++)
		free((unsigned char *)*cit);

	std::vector<float *>::reverse_iterator xyit;
	for(xyit = dropletPos->rbegin(); xyit != dropletPos->rend(); xyit++)
		free((float *)*xyit);

	delete dropletColors;
	delete dropletPos;

	return OGL_SUCCESS;
}

OGLResult DropletRenderer::createVAO()
{
	GLenum errorCheckValue = glGetError();
	glGenVertexArrays(1, &vaoID);
	glBindVertexArray(vaoID);

	errorCheckValue = glGetError();
	if (errorCheckValue != GL_NO_ERROR)
	{
		fprintf(stderr, "[ERROR] Could not create a VAO: %s \n",
			gluErrorString(errorCheckValue));
		return OGL_ERROR;
	}

	return OGL_SUCCESS;
}

// Taken from : http://www.opengl-tutorial.org/beginners-tutorials/tutorial-2-the-first-triangle/
OGLResult DropletRenderer::loadShader(
	std::string shaderFilepath, 
	GLenum shaderType,
	GLuint *shaderID_out)
{
    // Create the shaders
	GLuint shaderID; 
    shaderID = glCreateShader(shaderType);
 
    // Read the Shader code from the file
    std::string shaderCode;
    std::ifstream shaderStream(shaderFilepath, std::ios::in);
    if(shaderStream.is_open())
    {
        std::string line = "";
        while(std::getline(shaderStream, line))
            shaderCode += "\n" + line;
        shaderStream.close();
    }
 
    GLint result = GL_FALSE;
    int infoLogLength;
 
    // Compile Shader
	printf("Compiling shader : %s\n", shaderFilepath.c_str());
    char const *sourcePointer = shaderCode.c_str();
    glShaderSource(shaderID, 1, &sourcePointer , NULL);
    glCompileShader(shaderID);
 
    // Check Shader
    glGetShaderiv(shaderID, GL_COMPILE_STATUS, &result);
    glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
    std::vector<char> shaderErrorMessage(infoLogLength);
    glGetShaderInfoLog(shaderID, infoLogLength, NULL, &shaderErrorMessage[0]);

	if(result == GL_FALSE)
		fprintf(stderr, "[FATAL] Shader Compile operation failed.\n");

    fprintf(stdout, "%s\n", &shaderErrorMessage[0]);
 
	*shaderID_out = shaderID;

	return OGL_SUCCESS;
}

// Taken from : http://www.opengl-tutorial.org/beginners-tutorials/tutorial-2-the-first-triangle/
// The program is created AND LINKED in this function. To use another program immediately
// after calling this function, unlink this one first.
OGLResult DropletRenderer::loadProgram(
	std::string vertexShaderFilepath, 
	std::string fragmentShaderFilepath)
{
	// Load the shaders
	loadShader(vertexShaderFilepath, GL_VERTEX_SHADER, &vertexShaderID);
	loadShader(fragmentShaderFilepath, GL_FRAGMENT_SHADER, &fragmentShaderID);

	// Link the program
    fprintf(stdout, "Linking program\n");
    programID = glCreateProgram();
	
	// Attach the shaders
	glAttachShader(programID, vertexShaderID);
	glAttachShader(programID, fragmentShaderID);
    glLinkProgram(programID);
 
    // Check the program
	GLint result = GL_FALSE;
	GLint uniforms = -1;
    int infoLogLength;
    glGetProgramiv(programID, GL_LINK_STATUS, &result);
	glGetProgramiv(programID, GL_ACTIVE_UNIFORMS, &uniforms);
    glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLogLength);
    std::vector<char> programErrorMessage( std::max(infoLogLength, int(1)) );
    glGetProgramInfoLog(programID, infoLogLength, NULL, &programErrorMessage[0]);
	
	if(result == GL_FALSE)
		fprintf(stderr, "[FATAL] Shader Compile operation failed.\n");

	printf("[DEBUG] Active Uniforms in programID %u = %i\n", programID, uniforms);
    printf("[DEBUG] Program Message(s) : %s\n", &programErrorMessage[0]);
 
    return OGL_SUCCESS;
}

OGLResult DropletRenderer::destroyShadersAndPrograms()
{
	GLenum errorValue = glGetError();
	glUseProgram(0);

	GLsizei count = 0;
	GLuint *shaderIDs = (GLuint *)malloc(sizeof(GLuint) * MAX_SHADERS_PER_PROGRAM);
	glGetAttachedShaders(programID, MAX_SHADERS_PER_PROGRAM, &count, shaderIDs);
	for(GLsizei i = 0; i < count; i++)
		glDetachShader(programID, shaderIDs[i]); 
	for(GLsizei i = 0; i < count; i++)
		glDeleteShader(shaderIDs[i]); 
		
	glDeleteProgram(programID);
	free(shaderIDs);

	errorValue = glGetError();
	if (errorValue != GL_NO_ERROR)
	{
		fprintf(
			stderr,
			"[ERROR] Could not destroy the shaders: %s \n",
			gluErrorString(errorValue)
		);

		return OGL_ERROR;
	}

	return OGL_SUCCESS;
}

OGLResult DropletRenderer::setupWalls() 
{
	// CREATE PERIMETER WALLS
	// NOTE : For now we assume the perimeter of the arena is rectangular
	float l = tileLength;
	float xTilePos, yTilePos;

	for(int j = 0; j < 2; j++)	
	{
		if(j < 1)
		{
			yTilePos = -l * numRowTiles / 2.0f;
			xTilePos = -l * numColTiles / 2.0f;
		}
		else
		{
			yTilePos = l * numRowTiles / 2.0f - l;
			xTilePos = l * numColTiles / 2.0f - l;
		}
		
		for(int i = 0; i < numRowTiles; i++)
		{
			std::string wallModelPath = std::string(modelDirectory);
			float xPos = j < 1 ? (-l * numColTiles / 2.0f) : (l * numColTiles / 2.0f);  
			
			RenderableObject *wallY = createRenderableObject(
				wallModelPath.append(WALLY_MODEL_FILE),
				glm::vec3(xPos, yTilePos, 0.0f),
				GL_ARRAY_BUFFER,
				GL_STATIC_DRAW,
				GL_TRIANGLE_STRIP,
				DEFAULT);
			wallY->geomData->SetScaleMat(glm::vec3(0.0f, l, wallHeight));

			objectCounter++;
			if(j < 1) yTilePos += l; else yTilePos -= l;
		}
		for(int i = 0; i < numColTiles; i++)
		{
			std::string wallModelPath = std::string(modelDirectory);
			float yPos = j < 1 ? (l * numColTiles / 2.0f) : (-l * numColTiles / 2.0f);  
			
			RenderableObject *wallX = createRenderableObject(
				wallModelPath.append(WALLX_MODEL_FILE),
				glm::vec3(xTilePos, yPos, 0.0f),
				GL_ARRAY_BUFFER,
				GL_STATIC_DRAW,
				GL_TRIANGLE_STRIP,
				DEFAULT);
			wallX->geomData->SetScaleMat(glm::vec3(l, 0.0f, wallHeight));

			objectCounter++;
			if(j < 1) xTilePos += l; else xTilePos -= l;
		}
	}

	return OGL_SUCCESS; 
}

OGLResult DropletRenderer::setupFloor() 
{
	// Start from the top left tile's bottom left vertex
	float l = tileLength;
	float xTilePos = -l * numColTiles / 2.0f;
	float yTilePos = (l * numRowTiles / 2.0f) - l;

	// CREATE FLOOR TILES
	for(int i = 0; i < numRowTiles * numColTiles; i++)
	{
		if(xTilePos >= (l * numColTiles / 2.0f))
		{
			xTilePos = -l * numColTiles / 2.0f;
			yTilePos -= l;
		}
		
		std::string floorModelPath = std::string(modelDirectory);
		RenderableObject *floor = createRenderableObject(
			floorModelPath.append(FLOOR_MODEL_FILE),
			glm::vec3(xTilePos, yTilePos, 0.0f),
			GL_ARRAY_BUFFER,
			GL_STATIC_DRAW,
			GL_TRIANGLE_STRIP,
			DEFAULT);
		floor->geomData->SetScaleMat(glm::vec3(l, l, 0.0f));

		xTilePos += l;
		objectCounter++;
	}
	return OGL_SUCCESS; 
}

OGLResult DropletRenderer::setupDroplets() 
{ 
	float l = tileLength;
	float floorWidth = l * numColTiles;
	float floorLength = l * numRowTiles;
	float posRangeWidth = floorWidth / 2.0f;
	float posRangeLength = floorLength / 2.0f;
	for(int i = 0; i < numDroplets; i++)
	{
		// Set up the renderer model
		glm::vec3 startPosition = glm::vec3(
			getRandomf(-posRangeWidth + dropletRadius, posRangeWidth - dropletRadius),
			getRandomf(-posRangeLength + dropletRadius, posRangeLength - dropletRadius), 
			0.0f
		);
		std::string dropletModelPath = std::string(modelDirectory);
		RenderableObject *droplet = createRenderableObject(
			dropletModelPath.append(DROPLET_MODEL_FILE),
			startPosition,
			GL_ARRAY_BUFFER,
			GL_STREAM_DRAW,
			GL_TRIANGLE_FAN,
			DROPLET
		);
		droplet->geomData->SetScaleMat(glm::vec3(
			dropletRadius, 
			dropletRadius,
			1.0f)
		);
		objectCounter++;
	}
	
	glUseProgram(programID);

	printf("[DEBUG] Total Objects initialized = %i\n", objectCounter);
	printf("[DEBUG] Total Drawable objects created = %i\n", renderableObjects.size());
	return OGL_SUCCESS; 

}


RenderableObject *DropletRenderer::createRenderableObject(
	std::string modelFilepath,
	glm::vec3 startPosition,
	GLenum dataType,
	GLenum usageType,
	GLenum drawType,
	RenderableObjectType objType)
{
	RenderableObject *rObj;
	switch(objType)
	{
	case DEFAULT :
		rObj = new RenderableObject(
			new OGLDataObject(),
			new OGLGeometryData(startPosition)	// Set starting position
		);
		break;
	case DROPLET :
		rObj = new RenderableDroplet(
			new OGLDataObject(),
			new OGLGeometryData(startPosition)	// Set starting position
		);
		break;
	default :
		rObj = new RenderableObject(
			new OGLDataObject(),
			new OGLGeometryData(startPosition)	// Set starting position
		);
		break;
	}

	rObj->objData->dataType = dataType;
	rObj->objData->usageType = usageType;
	rObj->objData->vaoID = vaoID;
	rObj->objData->programID = programID;
	rObj->geomData->drawType = drawType;

	// Load the model
	if(rObj->LoadModel(modelFilepath) != OGL_SUCCESS)
	{
		fprintf(stderr, "[WARNING] rObj Model initialization failed.\n");
	}
//	rObj->DEBUG_PrintVertexData();
	renderableObjects.push_back(rObj);

	return rObj;
}

void DropletRenderer::initSimulator(void)
{
	// Set up required values
	// TODO : Setting up these values should go in a different function
	numRowTiles		= DEFAULT_ROW_TILES;
	numColTiles		= DEFAULT_COL_TILES;
	numDroplets		= DEFAULT_NUM_DROPLETS;
	tileLength		= DEFAULT_TILE_LENGTH;
	dropletRadius	= DEFAULT_DROPLET_RADIUS;
	wallHeight		= DEFAULT_WALL_HEIGHT;
	wallWidth		= DEFAULT_WALL_WIDTH;
	fps				= DEFAULT_FPS;

	// Initialize Simulator
	SimSetupData setupData(
		numRowTiles, 
		numColTiles, 
		tileLength, 
		dropletRadius,
		fps,
		true);
	sim.Init(setupData);

	// Set up simulator phyiscs objects
	btCollisionShape *floorShape = new btStaticPlaneShape(btVector3(0, 1, 0) , 0);
	btCollisionShape *xWallShape = new btBoxShape(btVector3(
		btScalar(tileLength * numRowTiles), 
		btScalar(wallHeight), 
		btScalar(wallWidth)));
	btCollisionShape *yWallShape = new btBoxShape(btVector3(
		btScalar(wallWidth), 
		btScalar(wallHeight), 
		btScalar(tileLength * numColTiles)));
	btCollisionShape *dropletShape = new btCylinderShape(btVector3(
		dropletRadius, 
		0.5, 
		dropletRadius));

	sim.AddCollisionShape(floorShape, &btFloorShapeID);
	sim.AddCollisionShape(xWallShape, &btXWallShapeID);
	sim.AddCollisionShape(yWallShape, &btYWallShapeID);
	sim.AddCollisionShape(dropletShape, &btDropletShapeID);

}

void DropletRenderer::setupSimObjects()
{
	// Create the floor and walls
	sim.CreateFloor(btFloorShapeID, btXWallShapeID, btYWallShapeID);

	float floorWidth = tileLength * numColTiles;
	float floorLength = tileLength * numRowTiles;
	float posRangeWidth = floorWidth / 2.0f;
	float posRangeLength = floorLength / 2.0f;

	for(int i = 0; i < DEFAULT_NUM_DROPLETS; i++)
	{
		// Set up the simulator/physics model
		ObjectPhysicsData *dropletPhyDat = (ObjectPhysicsData *)malloc(sizeof(ObjectPhysicsData));
		dropletPhyDat->colShapeIndex = btDropletShapeID;
		dropletPhyDat->mass = 1.0;
		dropletPhyDat->localInertia = btVector3(0.0, 0.0, 0.0);
		dropletPhyDat->friction = 0.8f;
		
		IDroplet *newDroplet = new DropletRainbow(dropletPhyDat);
		sim.AddDroplet(
			newDroplet, 
			std::make_pair(			
				getRandomf(-posRangeWidth + dropletRadius, posRangeWidth - dropletRadius),
				getRandomf(-posRangeLength + dropletRadius, posRangeLength - dropletRadius)),
			0.0f
		);
	}
}

float DropletRenderer::getRandomf(float min, float max)
{
	float range = (max - min) + 1.0f;
	return min + float(range * rand() / (RAND_MAX + 1.0f));
}

float DropletRenderer::getRandomf()
{
	return (float)rand() / (float)RAND_MAX;
}